#include "../include/Surface.h"
#include "../include/Window.h"

#include <stdlib.h>
#include <iostream>
#include <string>
#include <SDL2/SDL_image.h>
#include <stdexcept>

namespace GUL {
    namespace System {
        namespace Graphic {

            Surface::Surface(Window& win) : m_Surface(NULL), m_ReferenceWindow(win) {

            }

            Surface::Surface(Surface& surf) : m_Surface(NULL), m_ReferenceWindow(surf.m_ReferenceWindow) {
                this->m_Surface = surf.m_Surface;
            }

            Surface::Surface(Window& win, SDL_Surface* surf) : m_Surface(NULL), m_ReferenceWindow(win) {
                if (surf == nullptr)
                    throw std::invalid_argument("Surface::Surface -> SDL_Surface have to be not a nullptr!!!\n");
                this->m_Surface = surf;
            }

            Surface::Surface(Window& win, UPair boundary) : m_Surface(NULL), m_ReferenceWindow(win) 
            {
                int bpp = 0, r =0xff,g=0xff,b=0xff,a=255;
                SDL_PixelFormatEnumToMasks(SDL_PIXELFORMAT_RGBA8888,&bpp,&r,&g,&b,&a);
                this->m_Surface = SDL_CreateRGBSurface(0, boundary.first, boundary.second, bpp, r, g, b, a);
                if(this->SurfaceAlphaMod(255) == -1)
                    throw std::runtime_error("No Valid Alpha value\n");
                if (this->m_Surface == nullptr)
                    throw std::runtime_error("Surface::Surface() Couldn't create RGBSurface... " + std::string(SDL_GetError()));
            }

            Surface::Surface(const Window& win, std::string path) : m_Surface(NULL), m_ReferenceWindow(const_cast<Window&>(win)) {
                this->Load(path);
            }

            Surface::~Surface() {
                if (this->m_Surface != NULL)
                    SDL_FreeSurface(this->m_Surface);
                this->m_Surface = nullptr;
            }

            void Surface::Load(const std::string& path) {
                if (this->m_ReferenceWindow.IsSDLRenderSupportEnabled())
                {
                    this->m_Surface = IMG_Load(path.c_str());
                }
                else {
                    //The final optimized image
                    SDL_Surface* optimizedSurface = NULL;

                    //Load image at specified path
                    SDL_Surface* loadedSurface = IMG_Load(path.c_str());
                    if (loadedSurface == NULL) {
                        throw std::runtime_error("Unable to load SDL_surface with IMG_Load(). SDL_Error->" + std::string(SDL_GetError()));
                    } else {
                        //Convert surface to screen format
                        optimizedSurface = SDL_ConvertSurface(loadedSurface, static_cast<SDL_Surface*> (const_cast<Window&>(this->m_ReferenceWindow))->format, SDL_RLEACCEL);

                        if (optimizedSurface == NULL) {
                            throw std::runtime_error("Unable to optimize image(" + path + ") SDL_Error->" + std::string(SDL_GetError()));
                            printf("Unable to optimize image %s! SDL Error: %s\n", path.c_str(), SDL_GetError());
                        }

                        //Get rid of old loaded surface
                        SDL_FreeSurface(loadedSurface);
                    }

                    this->m_Surface = optimizedSurface;
                }
            }

            void Surface::Save(const std::string& path) {
                IMG_SavePNG(this->m_Surface, path.c_str());
            }

            void Surface::Blit() {
                SDL_BlitSurface(*this, nullptr, this->m_ReferenceWindow, nullptr);
            }

            void Surface::BlitScaled() {
                SDL_BlitScaled(*this, nullptr, this->m_ReferenceWindow, nullptr);
            }

            void Surface::Blit(SDL_Rect* src, SDL_Rect* dest) {
                SDL_BlitSurface(*this, src, this->m_ReferenceWindow, dest);
            }

            void Surface::BlitScaled(SDL_Rect* src, SDL_Rect* dest) {
                SDL_BlitScaled(*this, src, this->m_ReferenceWindow, dest);
            }
            
            void Surface::SoftStretch(SDL_Rect* rectsrc, Surface& des, SDL_Rect* rectdes) {
                SDL_SoftStretch(this->m_Surface,rectsrc,des,rectdes);

            }
            
                        
            void Surface::BlitTo(Surface& surf) {
                SDL_BlitSurface(*this, nullptr, surf, nullptr);

            }

            void Surface::BlitScaledTo(Surface& surf) {
                SDL_BlitScaled(*this, nullptr, surf, nullptr);

            }

            void Surface::BlitScaledTo( SDL_Rect* src, Surface& surf, SDL_Rect* dest) {
                SDL_BlitScaled(*this, src, surf, dest);

            }

            void Surface::BlitTo(SDL_Rect*src, Surface& surf, SDL_Rect* dest) {
                SDL_BlitSurface(*this, src, surf, dest);

            }


            int Surface::Colorkey() {
                Uint32 tmp = 0;
                SDL_GetColorKey(this->m_Surface, &tmp);
                return tmp;
            }

            int Surface::Colorkey(int flag, Uint32 Key) {
                SDL_SetColorKey(this->m_Surface, flag, Key);
                return this->Colorkey();
            }

            int Surface::SurfaceColorMod(Uint8 r, Uint8 g, Uint8 b) {
                return SDL_SetSurfaceColorMod(this->m_Surface, r, g, b);
            }

            std::vector<Uint8>& Surface::SurfaceColorMod(Uint8* r, Uint8* g, Uint8* b) {
                std::vector<Uint8>* tmp = new std::vector<Uint8>;
                
                SDL_GetSurfaceColorMod(this->m_Surface, r, g, b);
                tmp->push_back(*r);
                tmp->push_back(*g);
                tmp->push_back(*b);
                return *tmp;
            }

            int Surface::SurfaceAlphaMod(Uint8 a) {
                return SDL_SetSurfaceAlphaMod(this->m_Surface, a);
            }

            Uint8 Surface::SurfaceAlphaMod() {
                Uint8 a = 0;
                ;
                SDL_GetSurfaceAlphaMod(this->m_Surface, &a);
                return a;
            }

            int Surface::Lock() {
                if(SDL_LockSurface(this->m_Surface) != 0)
                    throw std::runtime_error("Couldn't Lock Surface SDL Error: "+std::string(SDL_GetError()) );
                return 0;
            }

            void Surface::Unlock() {
                SDL_UnlockSurface(this->m_Surface);
            }
        }
    }

}